package com.itcam.project.service.impl;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itcam.iahcommon.enums.ErrorCode;
import com.itcam.iahcommon.exception.BusinessException;
import com.itcam.iahcommon.exception.ThrowUtils;
import com.itcam.iahcommon.model.vo.BiResponse;
import com.itcam.iahcommon.response.BaseResponse;
import com.itcam.iahcommon.utils.ResultUtils;
import com.itcam.project.bizmq.BiMessageProducer;
import com.itcam.project.entity.Chart;
import com.itcam.project.manager.AiManager;
import com.itcam.project.manager.RedisLimiterManager;
import com.itcam.project.mapper.ChartMapper;
import com.itcam.project.model.dto.chart.GenChartByAIRequest;
import com.itcam.project.model.enums.StatusEnum;
import com.itcam.project.service.ChartService;
import com.itcam.project.utils.ExcelUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;


/**
 * @author Cammy
 */
@Service
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
        implements ChartService {

    @Resource
    private AiManager aiManager;

    @Resource
    private RedisLimiterManager redisLimiterManager;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private BiMessageProducer biMessageProducer;

    @Override
    public BaseResponse<BiResponse> genChartByAI(MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, Long userId) {
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();

        // 校验
        // 如果分析目标为空，就抛出请求参数错误异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        // 如果名称不为空，并且名称长度大于100，就抛出异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");

        // 文件目录：根据业务、用户来划分
//        String uuid =UUID.randomUUID().toString().replace("-", "");
//        String uuid = RandomStringUtils.randomAlphanumeric(8);
//        String filename = uuid + "-" + multipartFile.getOriginalFilename();

        // 校验文件，首先拿到用户的请求文件，取到原始文件的大小
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();

        // 校验文件大小，定义一个常量1M
        final long ONE_MB = 1024 * 1024L;
        // 如果文件大小大于1MB，抛出异常
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");

        // 校验文件后缀，利用FileUtil工具类的getSuffix方法获取文件后缀名
        String suffix = FileUtil.getSuffix(originalFilename);
        // 定义合法的后缀名列表
        List<String> validFileSuffixList = Arrays.asList("png", "jpg", "svg", "webp", "jpeg");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名非法");

        // 限流判断，每个用户一个限流器
        redisLimiterManager.doRateLimit("genChartByAI_" + userId);

        // 指定一个模型id（把id写死，也可以定义一个常量）
//        long biModelId = 12346789L;

        /*
         * 用户的输入(参考)
           分析需求：
           分析网站用户的增长情况
           原始数据：
           日期,用户数
           1号,10
           2号,20
           3号,30
         **/

        // 构造用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");

        // 拼接分析目标
        String userGoal = goal;
        // 如果图表类型不为空
        if (StringUtils.isNotBlank(chartType)) {
            // 就将分析目标拼接上“请使用”+图表类型
            userGoal += "，请使用" + chartType;
        }
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        // 压缩后的数据（把multipartFile传进来）
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        // 拿到返回结果
        String result = aiManager.doChat(userInput.toString());
        // 对返回结果做拆分,按照5个中括号进行拆分
        String[] splits = result.split("【【【【【");
        // 拆分之后还要进行校验
        if (splits.length < 2) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
        }
        String genChart = splits[0].trim();
        String genResult = splits[1].trim();

        // 插入到数据库
        Chart chart = new Chart();
        // 图表名称
        chart.setName(name);
        // 分析目标
        chart.setGoal(goal);
        // 图表数据
        chart.setChartData(csvData);
        // 图表类型
        chart.setChartType(chartType);
        // 图表代码
        chart.setGenChart(genChart);
        // 结论
        chart.setGenResult(genResult);
        // 睡登录的，睡创建的
        chart.setUserId(userId);
        // 如果为真，保存图表到数据库中
        boolean saveResult = this.save(chart);
        // 如果为假，抛出异常，并提示“图表保存失败”
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");

        // 后封装到BiResponse里
        BiResponse biResponse = new BiResponse();
        // 设置一下拿到的结果
        biResponse.setGenChart(genChart);
        biResponse.setGenResult(genResult);
        // 把新生成的图表id拿到
        biResponse.setChartId(chart.getId());

        return ResultUtils.success(biResponse);
    }

    @Override
    public BaseResponse<BiResponse> genChartByAIAsync(MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, Long userId) {
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();

        // 校验
        // 如果分析目标为空，就抛出请求参数错误异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        // 如果名称不为空，并且名称长度大于100，就抛出异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");

        // 文件目录：根据业务、用户来划分
//        String uuid =UUID.randomUUID().toString().replace("-", "");
//        String uuid = RandomStringUtils.randomAlphanumeric(8);
//        String filename = uuid + "-" + multipartFile.getOriginalFilename();

        // 校验文件，首先拿到用户的请求文件，取到原始文件的大小
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();

        // 校验文件大小，定义一个常量1M
        final long ONE_MB = 1024 * 1024L;
        // 如果文件大小大于1MB，抛出异常
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");

        // 校验文件后缀，利用FileUtil工具类的getSuffix方法获取文件后缀名
        String suffix = FileUtil.getSuffix(originalFilename);
        // 定义合法的后缀名列表
        List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名非法");

        // 限流判断，每个用户一个限流器
        redisLimiterManager.doRateLimit("genChartByAI_" + userId);

        // 指定一个模型id（把id写死，也可以定义一个常量）
//        long biModelId = 12346789L;

        /*
         * 用户的输入(参考)
           分析需求：
           分析网站用户的增长情况
           原始数据：
           日期,用户数
           1号,10
           2号,20
           3号,30
         **/

        // 构造用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");

        // 拼接分析目标
        String userGoal = goal;
        // 如果图表类型不为空
        if (StringUtils.isNotBlank(chartType)) {
            // 就将分析目标拼接上“请使用”+图表类型
            userGoal += "，请使用" + chartType;
        }
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        // 压缩后的数据（把multipartFile传进来）
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        // 插入到数据库
        Chart chart = new Chart();
        // 图表名称
        chart.setName(name);
        // 分析目标
        chart.setGoal(goal);
        // 图表数据
        chart.setChartData(csvData);
        // 图表类型
        chart.setChartType(chartType);
        // 图表代码
//        chart.setGenChart(genChart);
        // 结论
//        chart.setGenResult(genResult);
        // 设置任务状态为排队中
        chart.setStatus(StatusEnum.WAITING);
        // 睡登录的，睡创建的
        chart.setUserId(userId);
        // 如果为真，保存图表到数据库中
        boolean saveResult = this.save(chart);
        // 如果为假，抛出异常，并提示“图表保存失败”
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");

        try {
            CompletableFuture.runAsync(() -> {
                // 先修改图表任务状态为“执行中”。等执行成功后，修改为“已完成”。保存执行结果；执行失败后，状态修改为“失败”，记录失败信息。
                Chart updateChart = new Chart();
                updateChart.setId(chart.getId());
                // 把任务状态改为执行中
                updateChart.setStatus(StatusEnum.RUNNING);
                boolean b = this.updateById(updateChart);
                // 如果提交失败（一般情况下，更新失败可能意味着你的数据库出问题了）
                if (!b) {
                    handleChartUpdateError(chart.getId(), "更新图表执行中状态失败");
                    return;
                }
                // 拿到返回结果
                String result = aiManager.doChat(userInput.toString());
                // 对返回结果做拆分,按照5个中括号进行拆分
                String[] splits = result.split("【【【【【");
                // 拆分之后还要进行校验
                if (splits.length < 2) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
                }
                String genChart = splits[0].trim();
                String genResult = splits[1].trim();

                // 更新数据库中的图表信息
                Chart updateChartResult = new Chart();
                updateChartResult.setId(chart.getId());
                updateChartResult.setGenChart(genChart);
                updateChartResult.setGenResult(genResult);
                updateChartResult.setStatus(StatusEnum.SUCCEED);
                boolean updateResult = this.updateById(updateChartResult);
                if (!updateResult) {
                    handleChartUpdateError(chart.getId(), "更新图表成功状态失败");
                }

            }, threadPoolExecutor);
        } catch (RejectedExecutionException e) {
            // 处理队列满的异常
            // 回滚数据库中的图表状态
            handleChartUpdateError(chart.getId(), "更新图表成功状态失败");
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "线程池队列已满，任务被拒绝");
        }

        // 后封装到BiResponse里
        BiResponse biResponse = new BiResponse();
        // 设置一下拿到的结果
//        biResponse.setGenChart(genChart);
//        biResponse.setGenResult(genResult);
        // 把新生成的图表id拿到
        biResponse.setChartId(chart.getId());

        return ResultUtils.success(biResponse);
    }

    @Override
    public BaseResponse<BiResponse> genChartByAIAsyncMq(MultipartFile multipartFile, GenChartByAIRequest genChartByAIRequest, Long userId) {
        String name = genChartByAIRequest.getName();
        String goal = genChartByAIRequest.getGoal();
        String chartType = genChartByAIRequest.getChartType();

        // 校验
        // 如果分析目标为空，就抛出请求参数错误异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空");
        // 如果名称不为空，并且名称长度大于100，就抛出异常，并给出提示
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");

        // 文件目录：根据业务、用户来划分
//        String uuid =UUID.randomUUID().toString().replace("-", "");
//        String uuid = RandomStringUtils.randomAlphanumeric(8);
//        String filename = uuid + "-" + multipartFile.getOriginalFilename();

        // 校验文件，首先拿到用户的请求文件，取到原始文件的大小
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();

        // 校验文件大小，定义一个常量1M
        final long ONE_MB = 1024 * 1024L;
        // 如果文件大小大于1MB，抛出异常
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");

        // 校验文件后缀，利用FileUtil工具类的getSuffix方法获取文件后缀名
        String suffix = FileUtil.getSuffix(originalFilename);
        // 定义合法的后缀名列表
        List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀名非法");

        // 限流判断，每个用户一个限流器
        redisLimiterManager.doRateLimit("genChartByAI_" + userId);

        // 指定一个模型id（把id写死，也可以定义一个常量）
//        long biModelId = 12346789L;

        /*
         * 用户的输入(参考)
           分析需求：
           分析网站用户的增长情况
           原始数据：
           日期,用户数
           1号,10
           2号,20
           3号,30
         **/

        // 构造用户输入
        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");

        // 拼接分析目标
        String userGoal = goal;
        // 如果图表类型不为空
        if (StringUtils.isNotBlank(chartType)) {
            // 就将分析目标拼接上“请使用”+图表类型
            userGoal += "，请使用" + chartType;
        }
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        // 压缩后的数据（把multipartFile传进来）
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        // 插入到数据库
        Chart chart = new Chart();
        // 图表名称
        chart.setName(name);
        // 分析目标
        chart.setGoal(goal);
        // 图表数据
        chart.setChartData(csvData);
        // 图表类型
        chart.setChartType(chartType);
        // 图表代码
//        chart.setGenChart(genChart);
        // 结论
//        chart.setGenResult(genResult);
        // 设置任务状态为排队中
        chart.setStatus(StatusEnum.WAITING);
        // 睡登录的，睡创建的
        chart.setUserId(userId);
        // 如果为真，保存图表到数据库中
        boolean saveResult = this.save(chart);
        // 如果为假，抛出异常，并提示“图表保存失败”
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");

        Long newChartId = chart.getId();
        biMessageProducer.sendMessage(String.valueOf(newChartId));

        // 后封装到BiResponse里
        BiResponse biResponse = new BiResponse();
        // 设置一下拿到的结果
//        biResponse.setGenChart(genChart);
//        biResponse.setGenResult(genResult);
        // 把新生成的图表id拿到
        biResponse.setChartId(newChartId);

        return ResultUtils.success(biResponse);
    }

    /**
     * 处理图表更新错误的方法。
     * 当图表更新操作失败时，调用此方法将图表的状态设置为失败，并记录错误信息。
     *
     * @param chartId     要更新的图表的ID
     * @param execMessage 执行过程中出现的错误信息
     */
    private void handleChartUpdateError(Long chartId, String execMessage) {
        // 创建一个新的 Chart 对象，用于更新数据库中的图表信息
        Chart updateChartResult = new Chart();
        // 设置要更新的图表的ID
        updateChartResult.setId(chartId);
        // 设置图表的状态为失败
        updateChartResult.setStatus(StatusEnum.FAILED);
        // 设置执行过程中出现的错误信息
        updateChartResult.setExecMessage(execMessage);
        // 调用 updateById 方法更新数据库中的图表信息
        boolean updateResult = this.updateById(updateChartResult);
        // 如果更新失败，记录错误日志
        if (!updateResult) {
            // 记录错误日志，包含图表ID和错误信息
            log.error("更新图表失败状态失败" + chartId + "，" + execMessage);
        }
    }


}




